Jx.declare('Jx.code.Compiler', {

	$requires: [

		'Jx.code.Parser',

		'Jx.code.Operation'

	],

	$actions: [

		'parse',

		'compile',

		'purge',

		'registerCompiled',

		'registerCompiledIf'

	],

	$helpers: {

		'Str': 'Jx.lib.String' // used for escaping ECMA strings


	},

	$onconstruct: function(Class){

		// synthesize onParse Method
		function ON_PARSE(production, lexeme){

			var owner = arguments.callee.owner,

				operation = this,

				ret = void(0),

				lexvalue, len;

			if (owner.callbackIndex[production]){

				lexvalue = lexeme.value;

				args = [operation, lexeme, lexvalue.length];

				args.push.apply(args, lexvalue);

				ret = owner[ owner.callbackPrefix + production ].apply(owner, args);

				args.splice(0, args.length);

				if (owner.trace){

					console.log('production: ['+ lexeme.production+ '] {', lexvalue.length, '}:', lexeme );

				}

			}

			if (ret === false){

				operation.setError('failed processing production ['+production+'] near: '+ lexeme.getCode());

			}

			return ret;

		}

		ON_PARSE.owner = this;

		this.__callback_Parse = ON_PARSE;

	},

	$ondestruct: function(Class){

		delete this.onParse.owner;

		delete this.onParse;

	},

	parser: 'Jx.code.Parser',

	callbackPrefix: '__parse_',

	callbackIndex: undefined,

	compiled: undefined,

	compiledLimit: 1000,

	compiledCounter: 0,

	bufferCompiled: false,

	trace: false,

	constructor: function(){

		this.callbackIndex = {};

		this.compiled = {};

		this.$super(arguments);

	},

	onConstruct: function(){

		this.initializeParser(this.parser);

		this.$super(arguments);

	},

	onDestroy: function(){

		Jx.destruct(

			this.parser,

			this.callbackIndex

		);

		this.purge();

		this.$super(arguments);

	},

	initializeParser: function(parser){

		var J = Jx;

		if (J.isString(parser, true) &&

			J.classExists(parser)

		){

			parser = J.isSingleton(parser) ?

				J.getClass(parser)

				:

				J.instantiate(parser);

		}

		if (J.instanceOf(parser, 'Jx.code.Parser')){

			this.initializeCallbacks(this.parser = parser);

		} else {

			this.parser = null;

			var name = this.$name();

			throw new TypeError('invalid parser ['+ parser+ '] for compiler '+ name);

		}

	},

	initializeCallbacks: function(parser){

		var productions = parser.getProductions(),

			me = this,

			index = me.callbackIndex,

			prefix = me.callbackPrefix,

			J = Jx;

		for(var method, name, l = productions.length; l--;){

			name = productions[l];

			method = prefix + name;

			if (method in me && J.isFunction(me[method])){

				index[ name ] = true;

			}

		}

	},

	allowCompile: function(o, code){

		var valid = o.valid = Jx.isString(code),

			allCompiled = this.compiled;

		o.returnValue = void(0);

		// check workflow
		if (valid){

			// purge before anything else
			if (this.bufferCompiled){

				//-- purge
				if (++this.compiledCounter > this.compiledLimit){

					this.purge();

				}

			}

			//-- directly assign
			if (allCompiled.hasOwnProperty(code)){

				o.execOn = false;

				o.execAfter = false;

				o.returnValue = allCompiled[code];

			//-- parse first!
			} else {

				var operation = this.parse(code);

				valid = o.valid = operation.isSuccessful();

				if (!valid){

					operation.destroy();

				} else {

					o.params[1] = operation;

				}

			}

		}

	},

	onCompile: function(o, operation){

		var params = operation.params,

			source = operation.source,

			codes = operation.codes,

			allCompiled = this.compiled;



		o.returnValue = this.registerCompiledIf(source, params.join(','), codes.join("\n"));

		o.execAfter = true;

	},

	onAfterCompile: function(o, operation){

		operation.destroy();

	},



	allowRegisterCompiled: function(o, code, source, sourceParams){

		var J = Jx,

			S = this.__Str,

			params = o.params;

		o.valid = J.isString(code) && !!(code = S.trim(code)) &&

			J.isString(source) && !!(source = S.trim(source));

		params[1] = code;

		params[2] = source;

		params[3] = J.isArray(sourceParams) ?

			sourceParams

			:

			[];

		o.returnValue = void(0);

	},

	onRegisterCompiled: function(o, code, source, sourceParams){

		var allCompiled = this.compiled;

		// new
		if (!(code in allCompiled)){

			this.compiledCounter++;

		}

		o.returnValue = allCompiled[code] = new Function(sourceParams.join(','), source);

	},

	allowRegisterCompiledIf: function(o, code, source, sourceParams){

		this.allowRegisterCompiled(o, code, source, sourceParams);

		var allCompiled = this.compiled;

		code = o.params[1];

		if (o.valid && code in allCompiled){

			o.returnValue = allCompiled[code];

			o.valid = false;

		} else {

			o.returnValue = void(0);

		}

	},

	onRegisterCompiledIf: function(o, code, source, sourceParams){

		this.onRegisterCompiled(o, code, source, sourceParams);

	},

	allowParse: function(o, code){

		o.returnValue = void(0);

		var valid = o.valid = Jx.isString(code);

		if (valid){

			o.returnValue = o.params[1] = Jx.instantiate('Jx.code.Operation', [ code ]);

			o.execAfter = true;

		}

	},

	onParse: function(o, operation){

		this.parser.parse(operation.source, this.__callback_Parse, operation);

	},

	onAfterParse: function(o, operation){

		operation.evaluateParser( this.parser );

	},

	onPurge: function(){

		Jx.clearObject(this.compiled);

		delete this.compiledCounter;

	}

});
